<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>知识图谱 - 麻醉知识图谱</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <!--    导入bootstrap js文件-->
    <script src="https://cdn.staticfile.net/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
    
    <style>
        html {
            height: 100%;
        }

        body {
            font-family: 'Noto Sans SC', Arial, sans-serif;
            height: 100%;
            background-color: #f8f9fa;
            margin: 0;
            padding: 0;
            overflow-x: hidden;
        }

        .nav-button {
            background-color: #2E3B4E;
            color: white;
            font-size: 1.1rem;
            margin-right: 1rem;
            border: none;
            padding: 8px;
        }

        .nav-button:hover {
            background-color: #1F2A3A;
            border-radius: 5px;
        }

        .navbar-custom {
            background-color: #2E3B4E;
            padding: 1rem 2rem;
        }

        .navbar-custom .navbar-nav .nav-link {
            color: white;
            font-size: 1.1rem;
            margin-right: 1rem;
        }

        .navbar-custom .navbar-nav .nav-link:hover {
            background-color: #1F2A3A;
            border-radius: 5px;
        }

        #container {
            display: flex;
            width: auto;
            flex-direction: column;
            align-items: center;
            animation: fadeIn 2s;
            padding-top: 8rem;
        }

        #search-container {
            display: flex;
            justify-content: center;
            align-items: center;
            margin-bottom: 40px;
        }

        input[type="text"] {
            width: 300px;
            padding: 10px;
            margin-right: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            transition: border-color 0.3s;
        }

        input[type="text"]:focus {
            border-color: #2E3B4E;
        }

        button {
            padding: 10px 20px;
            background-color: #2E3B4E;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
            margin: 5px;
        }

        button:hover {
            background-color: #1F2A3A;
        }

        #legend {
            display: flex;
            justify-content: center;
            align-items: center;
            margin-bottom: 20px;
        }

        .legend-item {
            display: flex;
            align-items: center;
            margin: 0 10px;
            cursor: pointer;
        }

        .legend-color {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            margin-right: 5px;
        }

        #graph-container {
            width: 100%;
            height: 100%;
            position: relative;
            padding: 20px;
            background: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            animation: slideInUp 2s;
            /*margin-bottom: 10rem;*/
            margin-top: 30px;
        }

        #node-info {
            position: absolute;
            top: 200px;
            right: 10px;
            width: 400px;
            padding: 15px;
            background: white;
            border: 1px solid #ccc;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            display: none;
            font-family: 'Arial', sans-serif;
            font-size: 16px;
            color: #333;
            max-height: 70vh;
            /* 当内容超出时，整个面板会出现滚动条 */
        }

        #description-container {
            max-height: 30%;
            /* 设置简介部分占面板的30%最大高度 */
            overflow-y: auto;
            /* 当简介内容超出最大高度时，显示滚动条 */
        }

        #node-description {
            display: block;
            max-height: 150px;
            /* 简介的最大高度 */
            overflow-y: auto;
            /* 当简介内容超出高度时，显示滚动条 */
        }

        #node-info h3 {
            margin-top: 0;
            font-size: 20px;
            color: #2E3B4E;
            text-align: center;
            position: relative;
        }

        #node-info h3::after {
            content: '';
            display: block;
            width: 100px;
            height: 3px;
            background: black;
            margin: 10px auto 10px;
        }

        #node-info .info-item:nth-child(even) {
            background-color: #F5F5F5;
        }

        .info-item {
            margin-bottom: 10px;
        }

        #node-info .info-item {
            padding: 5px;
            background-color: white;
        }

        #node-info button {
            background-color: #1F2A3A;
            color: white;
            border: none;
            padding: 5px 20px;
            margin-top: 20px;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
            width: 100%;
        }

        #node-info button:hover {
            background-color: #444444;
        }

        #graph {
            width: 100%;
            height: 800px;
            border-radius: 4px;
            background: white;
        }

        .expand-icon {
            position: absolute;
            background-color: #2E3B4E;
            color: white;
            padding: 5px;
            border-radius: 50%;
            cursor: pointer;
            display: none;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
            }

            to {
                opacity: 1;
            }
        }

        @keyframes slideInUp {
            from {
                transform: translateY(100%);
            }

            to {
                transform: translateY(0);
            }
        }
    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-custom fixed-top">
        <div class="container-fluid">
            <a class="navbar-brand" href="/page">麻醉知识图谱</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link active" aria-current="page" href="/page">主页</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">知识图谱</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#" onclick="checkLoginAndRedirect('/profile')">个人中心</a>
                    </li>
                    <li class="nav-item">
                        <button id="logoutButton" class="nav-button"  onclick="logout()">注销</button>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div id="container">
        <div id="search-container">
            <input type="text" id="name" placeholder="请输入实体名称...">
            <button onclick="queryGraph()">查询</button>
            <button onclick="showExamplePatients(document.getElementById('name').value)">查看相关病历</button>
        </div>
        <div id="graph-container">
            <div id="legend"></div>
            <div id="graph"></div>
            <div id="expand-icon" class="expand-icon">+</div>
            <div id="node-info">
                <h3>节点信息</h3>
                <div class="info-item"><strong>编号:</strong> <span id="node-id"></span></div>
                <div class="info-item"><strong>名称:</strong> <span id="node-name"></span></div>
                <div class="info-item"><strong>类别:</strong> <span id="node-group"></span></div>
                <!-- 简介部分，设置滚动条，当内容过多时会出现 -->
                <div class="info-item" id="description-container">
                    <strong>简介:</strong>
                    <span id="node-description"></span>
                </div>
                <button onclick="showExamplePatients()">显示示例病人</button>
                <button onclick="toggleNodeInfo()">隐藏</button>
            </div>
        </div>
    </div>

    <script>
        let network;
        let allNodes = {};
        let activeNodes = {};
        let nodeGroups = {};
        let hiddenGroups = new Set();
        let expandedNodes = {};
        let lastClickedNode = null;

        function checkLoginAndRedirect(destination) {
            fetch('/check_login_status')
                .then(response => response.json())
                .then(data => {
                    if (!data.logged_in) {
                        alert("请先登录！");
                    } else {
                        if (destination == '/knowledge') {
                            if (data.userRole == 'admin') {
                                window.location.href = '/knowledge_admin';
                            } else {
                                window.location.href = '/knowledge';
                            }
                        } else {
                            window.location.href = destination;
                        }
                    }
                })
                .catch(error => {
                    console.error('检查登录状态时发生错误:', error);
                });
        }

        document.addEventListener('DOMContentLoaded', function () {
            fetchDefaultGraph();
        });

        async function fetchDefaultGraph() {
            const response = await fetch('/default-graph');
            if (!response.ok) {
                alert("无法加载默认图形数据，请重试");
                return;
            }
            const data = await response.json();
            const max_node_name_len = 10
            const nodes = data.nodes.map(node => ({
                id: node.id,
                label: node.properties.name.length > max_node_name_len ? node.properties.name.slice(0, max_node_name_len) + '...' : node.properties.name,
                fullName: node.properties.name,
                description: node.properties.description || "暂无简介",  // 简介字段
                group: node.types.join(', '),
                shape: 'circle',
                fixed: {
                    x: false,
                    y: false
                },
                font: {
                    size: 10,
                    color: '#ffffff',
                    align: 'center',
                    bold: true,
                    strokeWidth: 2,
                    strokeColor: '#000000'
                }
            }));

            const edges = data.relationships.map(rel => ({
                from: rel.start,
                to: rel.end,
                label: rel.type,
                arrows: 'to',
                font: {
                    size: 8,
                    align: 'horizontal'
                }
            }));

            allNodes = nodes.reduce((acc, node) => {
                acc[node.id] = node;
                return acc;
            }, {});

            activeNodes = {
                ...allNodes
            };

            nodeGroups = [...new Set(nodes.flatMap(node => node.group.split(', ')))];

            drawGraph(nodes, edges);
            createLegend();
        }

        async function queryGraph() {
            hideExpandIcon();

            const name = document.getElementById('name').value;
            if (!name) {
                alert("请输入实体名称");
                return;
            }
            const response = await fetch(`/query?name=${name}`);
            if (!response.ok) {
                alert("查询失败，请重试");
                return;
            }
            const data = await response.json();
            const max_node_name_len = 10
            const nodes = data.nodes.map(node => ({
                id: node.id,
                label: node.properties.name.length > max_node_name_len ? node.properties.name.slice(0, max_node_name_len) + '...' : node.properties.name,
                fullName: node.properties.name,
                description: node.properties.description || "暂无简介",  // 简介字段
                group: node.types.join(', '),
                shape: 'circle',
                fixed: {
                    x: false,
                    y: false
                },
                font: {
                    size: 10,
                    color: '#ffffff',
                    align: 'center',
                    strokeWidth: 2,
                    strokeColor: '#000000'
                }
            }));

            const edges = data.relationships.map(rel => ({
                from: rel.start,
                to: rel.end,
                label: rel.type,
                arrows: 'to',
                font: {
                    size: 8,
                    align: 'horizontal'
                }
            }));

            allNodes = nodes.reduce((acc, node) => {
                acc[node.id] = node;
                return acc;
            }, {});

            activeNodes = {
                ...allNodes
            };

            nodeGroups = [...new Set(nodes.flatMap(node => node.group.split(', ')))];

            drawGraph(nodes, edges);
            createLegend();
        }

        function drawGraph(nodes, edges) {
            const container = document.getElementById('graph');
            const data = {
                nodes: new vis.DataSet(nodes),
                edges: new vis.DataSet(edges)
            };
            const options = {
                nodes: {
                    shape: 'circle',
                    size: 30,
                    font: {
                        size: 10,
                        color: '#ffffff',
                        face: 'Microsoft YaHei, PingFang SC, Noto Sans SC, sans-serif',
                        strokeWidth: 2,
                        strokeColor: '#000000'
                    },
                    borderWidth: 2,
                    borderWidthSelected: 3,
                },
                edges: {
                    width: 1,
                    color: {
                        color: '#888888',
                        highlight: '#888888',
                        hover: '#888888'
                    },
                    smooth: {
                        type: 'continuous'
                    },
                    arrows: {
                        to: {
                            enabled: true,
                            scaleFactor: 0.8,
                            type: 'arrow'
                        }
                    },
                    font: {
                        size: 13,
                        face: 'Microsoft YaHei, PingFang SC, Noto Sans SC, sans-serif',
                        align: 'middle'
                    }
                },
                physics: {
                    stabilization: true,
                    forceAtlas2Based: {
                        gravitationalConstant: -10,
                        centralGravity: 0.001,
                        springLength: 128,
                        springConstant: 0.008,
                        damping: 2.5,
                        avoidOverlap: 3.5
                    },
                    solver: 'forceAtlas2Based'
                },
                interaction: {
                    tooltipDelay: 0,
                    hover: true
                }
            };
            network = new vis.Network(container, data, options);

            network.on("click", function (params) {
                if (params.nodes.length > 0) {
                    const nodeId = params.nodes[0];
                    const node = allNodes[nodeId];
                    if (lastClickedNode === nodeId) {
                        hideExpandIcon();
                        lastClickedNode = null;
                    } else {
                        displayNodeInfo(node);
                        showExpandIcon(nodeId, params.pointer.DOM);
                        lastClickedNode = nodeId;
                    }
                } else {
                    hideExpandIcon();
                }
            });

            network.on("dragEnd", function (params) {
                hideExpandIcon();
            });

            network.on("zoom", function (params) {
                hideExpandIcon();
            });
        }

        function createLegend() {
            const legend = document.getElementById('legend');
            legend.innerHTML = '';

            nodeGroups.forEach((group, index) => {
                const nodeId = Object.keys(network.body.nodes).find(id => allNodes[id].group.includes(group));
                const color = network.body.nodes[nodeId].options.color.background;

                const legendItem = document.createElement('div');
                legendItem.className = 'legend-item';
                legendItem.onclick = () => toggleCategory(group);

                const legendColor = document.createElement('div');
                legendColor.className = 'legend-color';
                legendColor.style.backgroundColor = color;

                const legendText = document.createElement('span');
                legendText.innerText = group;

                legendItem.appendChild(legendColor);
                legendItem.appendChild(legendText);

                legend.appendChild(legendItem);
            });
        }

        function toggleCategory(category) {
            const nodesToToggle = Object.values(allNodes).filter(node => node.group.includes(category));
            const currentNodeIds = new Set(network.body.data.nodes.getIds());

            if (hiddenGroups.has(category)) {
                nodesToToggle.forEach(node => {
                    if (!currentNodeIds.has(node.id)) {
                        network.body.data.nodes.add(node);
                    }
                });
                hiddenGroups.delete(category);
            } else {
                nodesToToggle.forEach(node => {
                    if (currentNodeIds.has(node.id)) {
                        network.body.data.nodes.remove(node.id);
                    }
                });
                hiddenGroups.add(category);
            }
        }

        function updateLegend() {
            const currentGroups = [...new Set(Object.values(allNodes).flatMap(node => node.group.split(', ')))];
            nodeGroups = currentGroups;
            createLegend();
        }

        function displayNodeInfo(node) {
            const nodeInfoDiv = document.getElementById('node-info');
            document.getElementById('node-id').innerText = node.id;
            document.getElementById('node-name').innerText = node.fullName;
            document.getElementById('node-group').innerText = node.group;
            document.getElementById('node-description').innerText = node.description;  // 新增展示简介部分
            nodeInfoDiv.style.display = 'block';
        }

        async function showExamplePatients(s_name='') {
            const nodeId = lastClickedNode;  // 使用最近点击的节点ID
            const node = allNodes[nodeId];  // 获取节点信息
            const search_name = ((s_name == '') ? node.fullName : s_name); // 用来存储sql首页出院病历模糊搜索的疾病
            const flag = ((s_name == '') ? 0 : 1)
            const response = await fetch(`/patient?search_name=${search_name}&flag=${flag}`);
            if (!response.ok) {
                alert("未找到相关病历");
                return;
            }
            const data = await response.json();  // 获取到的 data 是字典列表\

            const popup = document.createElement('div');
            popup.style.position = 'fixed';
            popup.style.top = '50%';
            popup.style.left = '50%';
            popup.style.transform = 'translate(-50%, -50%)';
            popup.style.backgroundColor = 'white';
            popup.style.border = '2px solid #ccc';
            popup.style.borderRadius = '8px';
            popup.style.padding = '20px';
            popup.style.zIndex = '1000';
            popup.style.width = '80%';
            popup.style.height = '80%';
            popup.style.overflowY = 'scroll';
            popup.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';

            // 关闭按钮，放在弹出窗口的右上角，始终可见
            const closeButton = document.createElement('span');
            closeButton.innerHTML = '&times;';
            closeButton.style.position = 'sticky';
            closeButton.style.top = '0px';
            closeButton.style.right = '0px';
            closeButton.style.cursor = 'pointer';
            closeButton.style.fontSize = '24px';
            closeButton.style.float = 'right';
            closeButton.onclick = () => document.body.removeChild(popup);

            // 将关闭按钮添加到弹出窗口的顶部
            popup.appendChild(closeButton);

            // 创建表格
            const table = document.createElement('table');
            table.className = 'table table-bordered table-striped';
            const header = table.createTHead();
            const headerRow = header.insertRow(0);
            const headers = data.shown_headers;

            headers.forEach(text => {
                const cell = document.createElement('th');
                cell.innerText = text;
                headerRow.appendChild(cell);
            });

            const body = table.createTBody();

            data.patients.forEach(row => {
                const tableRow = body.insertRow();
                headers.slice(0, -1).forEach(header => {
                    const cell = tableRow.insertCell();
                    cell.innerText = row[header];
                });

                // 创建“详情”按钮
                const detailCell = tableRow.insertCell();
                const detailButton = document.createElement('button');
                detailButton.className = 'btn btn-primary';
                detailButton.innerText = '详情';
                detailButton.onclick = () => showPatientDetails(row, data.order_headers);
                detailCell.appendChild(detailButton);
            });

            popup.appendChild(table);

            document.body.appendChild(popup);
        }

        function showPatientDetails(patientData, order_headers) {
            const detailPopup = document.createElement('div');
            detailPopup.style.position = 'fixed';
            detailPopup.style.top = '50%';
            detailPopup.style.left = '50%';
            detailPopup.style.transform = 'translate(-50%, -50%)';
            detailPopup.style.backgroundColor = 'white';
            detailPopup.style.border = '2px solid #ccc';
            detailPopup.style.borderRadius = '8px';
            detailPopup.style.padding = '20px';
            detailPopup.style.zIndex = '1000';
            detailPopup.style.width = '50%';
            detailPopup.style.height = '50%';
            detailPopup.style.overflowY = 'scroll';
            detailPopup.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';

            // 关闭按钮，放在弹出窗口的右上角，始终可见
            const closeButton = document.createElement('span');
            closeButton.innerHTML = '&times;';
            closeButton.style.position = 'sticky';
            closeButton.style.top = '0px';
            closeButton.style.right = '0px';
            closeButton.style.cursor = 'pointer';
            closeButton.style.fontSize = '24px';
            closeButton.style.float = 'right';
            closeButton.onclick = () => document.body.removeChild(detailPopup);

            // 将关闭按钮添加到弹出窗口的顶部
            detailPopup.appendChild(closeButton);

            // 创建表格
            const table = document.createElement('table');
            table.className = 'table table-bordered';
            order_headers.forEach(order_header => {
                const row = table.insertRow();
                const cell1 = row.insertCell(0);
                cell1.innerText = order_header;

                const cell2 = row.insertCell(1);
                cell2.innerText = patientData[order_header];
            });

            detailPopup.appendChild(table);

            document.body.appendChild(detailPopup);
        }

        function toggleNodeInfo() {
            const nodeInfoDiv = document.getElementById('node-info');
            if (nodeInfoDiv.style.display === 'block') {
                nodeInfoDiv.style.display = 'none';
            } else {
                nodeInfoDiv.style.display = 'block';
            }
        }

        function showExpandIcon(nodeId, position) {
            const expandIcon = document.getElementById('expand-icon');
            expandIcon.style.left = (position.x + 10) + 'px';
            expandIcon.style.top = (position.y - 10) + 'px';
            expandIcon.style.display = 'block';
            expandIcon.onclick = () => toggleExpandNode(nodeId);
        }

        function hideExpandIcon() {
            const expandIcon = document.getElementById('expand-icon');
            expandIcon.style.display = 'none';
        }

        async function toggleExpandNode(nodeId) {
            if (expandedNodes[nodeId]) {
                collapseNode(nodeId);
                hideExpandIcon();
            } else {
                await expandNode(nodeId);
                hideExpandIcon();
            }
        }

        async function expandNode(nodeId) {
            const response = await fetch(`/expand?id=${nodeId}`);
            if (!response.ok) {
                alert("扩展节点失败，请重试");
                return;
            }
            const data = await response.json();

            expandedNodes[nodeId] = {
                nodes: [],
                edges: []
            };
            const max_node_name_len = 10
            data.nodes.forEach(node => {
                if (!allNodes[node.id]) {
                    allNodes[node.id] = {
                        id: node.id,
                        label: node.properties.name.length > max_node_name_len ? node.properties.name.slice(0, max_node_name_len) + '...' : node.properties.name,
                        fullName: node.properties.name,
                        description: node.properties.description || "暂无简介",  // 简介字段
                        group: node.types.join(', '),
                        shape: 'circle',
                        fixed: {
                            x: false,
                            y: false
                        },
                        font: {
                            size: 10,
                            color: '#ffffff',
                            align: 'center',
                            strokeWidth: 2,
                            strokeColor: '#000000'
                        }
                    };
                    network.body.data.nodes.add(allNodes[node.id]);
                    expandedNodes[nodeId].nodes.push(node.id);
                }
            });

            data.relationships.forEach(rel => {
                network.body.data.edges.add({
                    from: rel.start,
                    to: rel.end,
                    label: rel.type,
                    arrows: 'to',
                    font: {
                        size: 8,
                        align: 'horizontal'
                    }
                });

                expandedNodes[nodeId].edges.push(rel.id);
            });

            expandedNodes[nodeId].expanded = true;
            updateLegend();
        }

        function collapseNode(nodeId) {
            const nodesToRemove = expandedNodes[nodeId].nodes;
            const edgesToRemove = expandedNodes[nodeId].edges;

            nodesToRemove.forEach(id => {
                network.body.data.nodes.remove(id);
                delete allNodes[id];
            });

            edgesToRemove.forEach(edgeId => {
                network.body.data.edges.remove(edgeId);
            });

            delete expandedNodes[nodeId];
            updateLegend();
        }
        function logout() {
        fetch('/logout')
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    window.location.href = '/';
                } else {
                    alert(data.error);
                }
            });
        }
    </script>
</body>

</html>